import pdfplumber
from typing import Optional
from book import Book, Page, Content, ContentType, TableContent, ImageContent, Row, Cell
from translator.exceptions import PageOutOfRangeException
from utils import LOG
import os

class PDFParser:
    def __init__(self, line_spacing_threshold: int = 50):
        self.line_spacing_threshold = line_spacing_threshold

    def parse_pdf(self, pdf_file_path: str, pages: Optional[int] = None) -> Book:
        book = Book(pdf_file_path)

        with pdfplumber.open(pdf_file_path) as pdf:
            self._validate_page_range(pdf, pages)
            pages_to_parse = pdf.pages[:pages] if pages else pdf.pages

            for page_number, pdf_page in enumerate(pages_to_parse):
                page = self._parse_page(pdf_page, page_number, pdf_file_path)
                book.add_page(page)

        return book

    def _validate_page_range(self, pdf, pages: Optional[int]):
        if pages is not None and pages > len(pdf.pages):
            raise PageOutOfRangeException(len(pdf.pages), pages)

    def _parse_page(self, pdf_page, page_number: int, pdf_file_path: str) -> Page:
        page = Page()
        contents = []

        char_data = pdf_page.chars
        table_bounds, tables = self._extract_tables(pdf_page)

        self._process_text(char_data, table_bounds, contents)
        self._process_tables(tables, contents)
        self._process_images(pdf_page, page_number, pdf_file_path, contents)

        contents.sort(key=lambda content: content.position[1])
        for content in contents:
            page.add_content(content)

        return page

    def _extract_tables(self, pdf_page):
        all_tables = pdf_page.find_tables()
        table_bounds = [table.bbox for table in all_tables]
        tables = self._parse_tables_content(all_tables)
        return table_bounds, tables

    def _process_text(self, char_data, table_bounds, contents):
       
        for block in self._group_chars_into_blocks(char_data):
             if not self._is_within_table(block["position"], table_bounds) and block['text'].strip():
                text_content = Content(
                    content_type=ContentType.TEXT,
                    original=block["text"],
                    font_size=block["font_size"],
                    is_bold=block["is_bold"],
                    position=block["position"],
                )
                contents.append(text_content)
                LOG.debug(f"[Text Block: {block['text']}, Position: {block['position']}]")

    def _process_tables(self, tables, contents):
        for table_content in tables:
            LOG.debug(f"[Table Found]\n{table_content}")
            contents.append(table_content)

    def _process_images(self, pdf_page, page_number: int, pdf_file_path: str, contents):
        parent_dir = os.path.dirname(pdf_file_path)
        for img_index, image_info in enumerate(pdf_page.images):
            try:
                image_path = self._save_image(pdf_page, img_index, image_info, page_number, parent_dir)
                image_content = ImageContent(
                    image_path=image_path,
                    image_name=os.path.basename(image_path),
                    width=image_info["width"],
                    height=image_info["height"],
                    position=(
                        image_info["x0"],
                        image_info["top"],
                        image_info["x0"] + image_info["width"],
                        image_info["top"] + image_info["height"],
                    ),
                )
                contents.append(image_content)
            except Exception as e:
                LOG.error(f"Failed to save image on page {page_number + 1}: {e}")

    def _save_image(self, pdf_page, img_index: int, image_info, page_number: int, parent_dir: str) -> str:
        bbox = (image_info["x0"], image_info["top"], image_info["x1"], image_info["bottom"])
        cropped_page = pdf_page.within_bbox(bbox)
        img_obj = cropped_page.to_image()
        image_filename = f"page_{page_number + 1}_image_{img_index + 1}.png"
        image_path = os.path.join(parent_dir, image_filename)
        img_obj.save(image_path)
        return image_path

    def _group_chars_into_blocks(self, char_data):
        """
        将字符数据分组为文本块，并保留格式和布局信息。
        """
        # 先按照y坐标（top）排序，然后按x坐标排序
        char_data = sorted(char_data, key=lambda c: (c["top"], c["x0"]))

        current_block = None
        previous_char = None

        for char in char_data:
            font_size = char["size"]
            is_bold = "Bold" in char["fontname"]
            position = (char["x0"], char["top"], char["x0"] + char["width"], char["top"] + char["height"])

            if current_block is None or self._is_new_block(current_block, font_size, is_bold, position, previous_char):
                if current_block:
                    yield current_block
                current_block = {
                    "text": char["text"],
                    "font_size": font_size,
                    "is_bold": is_bold,
                    "position": position,
                    "line_count": 1  # 初始化行数为1
                }
            else:
                current_block["text"] += char["text"]
                current_block["position"] = (
                    current_block["position"][0],
                    current_block["position"][1],
                    max(current_block["position"][2], char["x0"] + char["width"]),
                    max(current_block["position"][3], char["top"] + char["height"])
                )
                current_block["line_count"] += 1  # 增加行数

            previous_char = char

        if current_block:
            yield current_block

    def _is_new_block(self, current_block, font_size, is_bold, position, previous_char):
        """
        检查是否需要开始一个新的文本块。
        """
        if previous_char is None:
            return False

        line_spacing = abs(previous_char["bottom"] - position[1])
        #行间距大于阈值
        not_in_line_spacing_threshold = line_spacing > self.line_spacing_threshold
        #字体改变
        font_size_changed = font_size != current_block["font_size"]
        #粗细
        bold_status_changed = is_bold != current_block["is_bold"]

        if(font_size_changed or bold_status_changed or not_in_line_spacing_threshold):
            LOG.debug(f"not_in_line_spacing_threshold:{not_in_line_spacing_threshold} font_size_changed:{font_size_changed} bold_status_changed:{bold_status_changed}  ")
            LOG.debug(current_block)
        return font_size_changed or bold_status_changed or not_in_line_spacing_threshold


    def _is_within_table(self, position, table_bounds):
        x, y, _, _ = position
        return any(bbox[0] <= x <= bbox[2] and bbox[1] <= y <= bbox[3] for bbox in table_bounds)

    def _parse_tables_content(self, all_tables):
        tables = []
        for table in all_tables:
            table_content = table.extract()
            table_rows = []

            for row_index, row in enumerate(table.rows):
                row_content = Row()

                for cell_index, cell in enumerate(row.cells):
                    x1, y1, x2, y2 = cell
                    cell_width = x2 - x1
                    cell_height = y2 - y1
                    cell_content = table_content[row_index][cell_index]
                    cell = Cell(
                        original=cell_content, width=cell_width, heigth=cell_height
                    )
                    row_content.add_cell(cell)
                table_rows.append(row_content)

            table_content = TableContent(
                data=table_rows,
                position=table.bbox,
            )
            tables.append(table_content)
        return tables
